
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include <engine.h>
#include <vfs.h>
#include <sharedvalue.h>
#include <sharedvaluemanager.h>
#include <sstream>
#include <math/matrix.h>
#include <math/quaternion.h>
#include <SDL.h>
#include <GL/glew.h>
#include <asset/assetsystem.h>
#include <gfx/light.h>
#include <gfx/material.h>
#include <gfx/mesh.h>
#include <gfx/ieffect.h>
#include <gfx/image.h>
#include <gfx/itexture.h>
#include <gfx/iindexbuffer.h>
#include <gfx/iprogram.h>
#include <gfx/ishader.h>
#include <gfx/ivertexbuffer.h>
#include <gfx/ivertexdeclaration.h>
#include <gfx/programparametername.h>
#include <gfx/projector.h>
#include <gfx/texturepack.h>
#include <gfx/vertex.h>
#include <gfx/generator/grassinstancegenerator.h>
#include <gfx/gl20/renderloop.h>
#include <manager.h>
#include <physics/physicsworld.h>
#include <scene/billboardanimator.h>
#include <scene/entitynode.h>
#include <scene/impostereffectcamera.h>
#include <scene/geometry.h>
#include <scene/node.h>
#include <scene/light.h>
#include <scene/lodnode.h>
#include <scene/portal.h>
#include <scene/renderbucket.h>
#include <scene/scene.h>
#include <scene/sector.h>
#include <scene/skeleton.h>
#include <scene/skeletonbone.h>
#include <scene/sky.h>
#include <utils/lodgenerator.h>
#include <utils/meshcreator.h>
#include <utils/transformer.h>


#define FULLSCREEN  0

#if FULLSCREEN

#ifdef CC_WIN32
// this is no limitation for windows but my windows development machine doesn't has more
// and those settings are not yet read from a settings file.
#define WINDOW_WIDTH 1680
#define WINDOW_HEIGHT 945
#else
#define WINDOW_WIDTH 1680
#define WINDOW_HEIGHT 1050
#endif

#else

#define WINDOW_WIDTH 1024
#define WINDOW_HEIGHT 768

#endif


#ifdef __WIN32__
#include <windows.h>

#ifdef LoadImage
#undef LoadImage
#define LoadImage LoadImage
#endif

#endif


ceEngine engine;
ceEntityNode* cameraNode;

iRenderTarget* renderTarget;
iTexture* texture;

ceProjector projector;
cePortal*	portal = 0;
ceScene   scene;
cmPhysicsWorld  *physWorld = 0;
ceMeshCreator mc;

ceNode* cubeParent = 0;
ceNode* cubeParent1 = 0;

ceEntityNode* movingLightNode = 0;
ceRenderLoop* renderLoop = 0;
ceSharedValueFloat*		svTime = 0;
ceSharedValueVector4*	svWind = 0;
ceSharedValueVector3* svSunPos = 0;

ceSkeleton *skeleton = 0;

float effectEval = 0.0f;
float textureEval = 0.0f;
bool renderWireframe = false;
bool renderBoundingBoxes = false;

bool keyA = false;
bool keyD = false;
bool keyS = false;
bool keyW = false;
int  relX = 0;
int  relY = 0;
bool moveFast = false;

unsigned timeStart = 0;
unsigned timeValue = 0;

void debug_matrix (const ceMatrix4f& mat);
/*
class ceParticleGenerator : public iInstanceGenerator
{
  CE_OBJECT
private:
	unsigned		_count;
  ceVector3*	_vertices;
	unsigned		_num;

	struct Particle
	{
		unsigned	Index;
    ceVector3	Direction;
		unsigned	Time;
	};
	
	Particle*		_particles;
  ceVector3		_gravity;
	
public:
  ceParticleGenerator (unsigned num)
		: iInstanceGenerator ()
		, _num (num)
		, _count (0)
	{
    CE_OBJECT_CONSTR;
		_particles = new Particle [num];
    _vertices = new ceVector3 [num];
		for (unsigned i=0; i<num; i++)
			{
				_particles[i].Time = 0;
				_particles[i].Index = i;
        _particles[i].Direction = ceVector3 (0.0f, 0.01f, 0.0f);
			}
    _gravity = ceVector3 (0, 0, 0);
	}

  virtual void Generate (const ceRenderEnvironment& env,
                         iDevice*									device,
                         ceVector3**								instancePositions,
												 unsigned&									num, 
												 unsigned&									maxNum)
	{
		static unsigned p = 0;
		*instancePositions = _vertices;
		if (_count < _num)
			{
				if (!(++p % 100))
					{
						_count++;
					}
			}
		for (unsigned i=0; i<_count; i++)
			{
				Particle& ptl = _particles[i];
				ptl.Time = ptl.Time % 10000;
				if (ptl.Time == 0)
					{
            _vertices[ptl.Index] = ceVector3 (0, 0, 0);
					}
				ptl.Time++;
				_vertices[ptl.Index] += ptl.Direction + _gravity;
			}
		maxNum = _num;
		num = _count;

	}

};
*/

void clear_input ()
{
  relX = relY = 0;
}

bool handle_input (SDL_Event &e)
{
#define KEY(s,b,v) if (e.key.keysym.sym == s) b = v;
  switch (e.type)
    {
      case SDL_KEYDOWN:
        KEY(SDLK_a, keyA, true);
        KEY(SDLK_d, keyD, true);
        KEY(SDLK_s, keyS, true);
        KEY(SDLK_w, keyW, true);
        KEY(SDLK_LSHIFT, moveFast, true);
        break;
      case SDL_KEYUP:
        KEY(SDLK_a, keyA, false);
        KEY(SDLK_d, keyD, false);
        KEY(SDLK_s, keyS, false);
        KEY(SDLK_w, keyW, false);
        KEY(SDLK_LSHIFT, moveFast, false);
				if (e.key.keysym.sym == SDLK_u)
					{
						renderLoop->PerformUpdate (!renderLoop->IsPerformUpdate ());
					}
        if (e.key.keysym.sym == SDLK_i)
          {
            renderLoop->SetShadowEnabled(!renderLoop->IsShadowEnabled());
          }
				if (e.key.keysym.sym == SDLK_p && portal)
					{
						portal->SetOpen (!portal->IsOpen ());
					}
        break;
      case SDL_MOUSEMOTION:
        relX = e.motion.xrel;
        relY = -e.motion.yrel;
        break;
      }
#undef KEY
	return true;
}

void handle_camera ()
{
  static float rotH = 0.0f;
  static float rotV = 0.0f;

  float speed = moveFast ? 0.4f : 0.1f;

  ceVector3f dir;
  dir.x -= keyA ? speed : 0.0f;
  dir.x += keyD ? speed : 0.0f;
  dir.y -= keyS ? speed : 0.0f;
  dir.y += keyW ? speed : 0.0f;

  rotH -= (float)relX / 10.0f;
  rotV += (float)relY / 10.0f;

  ceVector3f origin = cameraNode->GetGlobalTranslation();

  cameraNode->ClearTransformation();
  cameraNode->Rotate(ceVector3f(0, 0, 1), rotH);
  cameraNode->Rotate(ceVector3f(1, 0, 0), rotV);
  cameraNode->SetTranslation(origin);
  cameraNode->Translate(dir);
  cameraNode->FinishTransformation(false);
}

void read_matrix (unsigned type, ceMatrix4f& mat)
{
	glGetFloatv(type, (GLfloat*)(mat.m));
}

ceMatrix4f get_matrix (unsigned type)
{
  ceMatrix4f mat;
	read_matrix (type, mat);
	return mat;
}

ceRef<iPatchMesh> create_terrain_mesh (ceEngine* engine, const char* filename, bool invertP, bool invertQ, float dp, float dq)
{
	ceImage* image = engine->GetAssetSystem()->GetAsset<ceImage>(filename);
	iDevice *device = engine->GetDevice();

  if (!image)
    {
      printf ("Unable to open image File: %s\n", filename); fflush (stdout);
      return 0;
    }

  float* heightData = new float [image->GetWidth() * image->GetHeight()];
  float* hptr = heightData;
	float maxH = 20.0f;
	maxH = 0.0f;
  for (unsigned q=0; q<image->GetHeight(); q++)
    {
      for (unsigned p=0; p<image->GetWidth(); p++)
        {
          unsigned rgb = image->GetRGB(p, q);

          *hptr++ = ((float)(rgb & 0xff) / 255.0f) * maxH;
        }
    }

	iPatchMesh* mesh = mc.CreateTerrain (
		ceVector3f (-dp/2.0f, -dq/2.0f, 0.0f),
		ceVector3f ( dp/2.0f,  dq/2.0f, 1.0f),
		image->GetWidth (),
		image->GetHeight (),
		heightData,
		4,
		4,
		100,
		invertP,
		invertQ,
		false);
	delete [] heightData;
	if (mesh)
		{
			mesh->CalcSmoothNormals ();
		}
	return mesh;
}



ceRef<iPatchMesh> create_sphere (iDevice* device, float radius, unsigned numP)
{
  ceVertexElement vertexElements []  =
    { ceVertexElement(ceProgramParameterName(PPB_Position0), DT_Float, 3, 0,  32, 0),
      ceVertexElement(ceProgramParameterName(PPB_Normal0), DT_Float, 3, 12, 32, 0),
      ceVertexElement(ceProgramParameterName(PPB_TexCoord0), DT_Float, 2, 24, 32, 0),
			ceVertexElement()
		};
  iVertexDeclaration* vertDecl = device->CreateVertexDeclaration(vertexElements);

  unsigned numQ = ((numP - 1) >> 1) + 1;

  float* vertices = new float[numP*numQ*8];
  float* vptr=vertices;
  for (unsigned q=0; q<numQ; q++)
    {
      float angQ = (float)q * M_PI / (float)(numQ-1);
      float tv = (float)q / (float)(numQ-1);
      for (unsigned p=0; p<numP; p++)
        {
          float angP = (float)p * (float)M_PI * 2.0f / (float)(numP - 1);
          float tu = (float)p / (float)(numP-1);
          ceVector3f po;
          po.x = sin(angP) * radius * sin(angQ);
          po.y = cos(angQ) * radius * 1.0f;
          po.z = cos(angP) * radius * sin(angQ);
          ceVector3f n = po.Normalized();

          *vptr++ = po.x;
          *vptr++ = po.y;
          *vptr++ = po.z;

          *vptr++ = n.x;
          *vptr++ = n.y;
          *vptr++ = n.z;

          *vptr++ = tu;
          *vptr++ = tv;
        }
    }


  unsigned splits = 8;

  iVertexBuffer* vb = device->CreateVertexBuffer(numP*numQ*8*sizeof(float), vertices, BDM_Static);
	delete [] vertices;

  iPatchMesh* patch = device->CreatePatchMesh();
	if (!patch->Initialize (device, vb, numP, numQ, vertDecl, splits, splits>>1, radius * 10.0, true, true))
		{
      CE_UNSET(patch);
		}
	return patch;
}


ceRef<iMesh> create_grass_mesh (iDevice* device,
                          float radius,
                          float height,
                          unsigned num,
                          const char* filename,
                          const char* densityFilename,
                          float p0,
                          float p1,
                          float q0,
                          float q1,
                          float dp,
                          float dq,
													bool invertP,
													bool invertQ)
{
	static unsigned grass_counter = 0;

  ceImage* image = 0;// ceLoadingSystem::Get()->LoadImage("", ceVFS::Get()->Open (filename));
  if (!image)
    {
      printf ("Unable to open image File\n"); fflush (stdout);
      return 0;
    }
  ceImage* densImage = 0; //ceLoadingSystem::Get()->LoadImage("", ceVFS::Get()->Open(densityFilename));
  if (!densImage)
    {
      printf ("Unable to open density image File\n"); fflush (stdout);
      return 0;
    }
  ceVertexElement vertexElements []  =
    { ceVertexElement(ceProgramParameterName(PPB_Position0), DT_Float, 3, 0,  32, 0),
      ceVertexElement(ceProgramParameterName(PPB_Normal0),	 DT_Float, 3, 12, 32, 0),
      ceVertexElement(ceProgramParameterName(PPB_TexCoord0), DT_Float, 2, 24, 32, 0),
			ceVertexElement()
		};
  iVertexDeclaration* vertDecl = device->CreateVertexDeclaration(vertexElements);

  unsigned vertexSize = 3 + 3 + 2;
  unsigned numVerts = 4;

  unsigned faceSize = 3;
  unsigned numFaces = 2;

  float* vertices = new float[vertexSize * numVerts * 3];
  unsigned short* indices = new unsigned short[numFaces * faceSize * 3];

#define GETV(j, r)   vertices[(i*numVerts + j)*vertexSize + r]
#define SETV(j, r, v) GETV(j, r) = v
#define VERT(j,x,y,z,u,v) SETV(j, 0, c*x); SETV(j, 1, y), SETV(j, 2, s*z); SETV(j, 3, 0); SETV(j, 4, 0); SETV(j, 5, 1); SETV(j, 6, u); SETV(j, 7, v)


#define SETI(r,v) indices[i*(numFaces*faceSize) + r] = i * numVerts + v
#define IND(i0, i1, i2, i3) SETI(0, i0); SETI(1, i1); SETI(2, i2); SETI(3, i0); SETI(4, i2); SETI(5, i3)
//                            SETI(6, i0); SETI(7, i2); SETI(8, i1); SETI(9, i0); SETI(10, i3); SETI(11, i2);
  for (unsigned i=0; i<3; i++)
    {
      float a = (float)i * (float)M_PI / 3.0f;
      float c = cos(a);
      float s = sin(a);

      IND(0, 1, 2, 3);
      VERT(0, -radius,  radius, 0,      0, 1);
      VERT(1, -radius,  radius, height, 0, 0);
      VERT(2,  radius, -radius, height, 1, 0);
      VERT(3,  radius, -radius, 0,      1, 1);

    }
#undef GETV
#undef SETV
#undef VERT
#undef SETI
#undef IND

  ceBoundingBox bbox;
  bbox.Add(ceVector3f (-radius, -radius, -radius));
  bbox.Add(ceVector3f (radius, radius, radius));
  bbox.Update();


  iVertexBuffer* vb = device->CreateVertexBuffer(vertexSize*numVerts*3*sizeof(float), vertices, BDM_Static);
  delete [] vertices;

  iIndexBuffer* ib = device->CreateIndexBuffer(numFaces*faceSize*3*sizeof(unsigned short), indices, BDM_Static);
  delete [] indices;

  ceVector3f* instancePositions = new ceVector3f [num];
  ceVector3f* vptr = instancePositions;
  float maxH = 20.0f;
  maxH = 0.0f;
  srand(0);
	ceBoundingBox ibbox;
	bbox.Clear();
#define RANDOM(min,max) ((float)(min) + ((float)rand () / (float)RAND_MAX) * (float)((max)-(min)))
  unsigned realNum = 0;
  for (unsigned i=0; i<num; i++)
    {
      float fip = RANDOM(p0, p1);
      float fiq = RANDOM(q0, q1);

			unsigned pos_p = fip * image->GetWidth();
			unsigned pos_q = fiq * image->GetHeight();
			if (invertP)
				{
					pos_p = image->GetWidth () - pos_p;
				}
			if (invertQ)
				{
					pos_q = image->GetHeight () - pos_q;
				}
      unsigned rgb = image->GetRGB(pos_p, pos_q);
      unsigned dens = densImage->GetRGB(fip * image->GetWidth(), fiq * image->GetHeight()) & 0xff;
      unsigned dr = RANDOM(0x00, 0xff);
      if (dr >= dens)
        {
          continue;
        }

      vptr->x = (dp/-2.0) + fip * dp;
      vptr->y = -((dq/-2.0) + fiq * dq);
      vptr->z = ((float)(rgb & 0xff) / 255.0f) * maxH;
			ibbox.Add (*vptr);
      vptr++;
      realNum++;
    }
#undef RANDOM
  ibbox.AddMargin (ceVector3f (radius, radius, radius));
	ibbox.Update ();

  ceMesh* mesh = new ceMesh();
  mesh->SetVertexDeclaration(vertDecl);
  mesh->SetVertices(vb);
  mesh->SetIndices(ib);
  mesh->Set32BitIndices(false);
  mesh->SetBoundingBox(bbox);
  mesh->SetNumberOfTrigons(3 * numFaces);

  CE_UNSET(vb);
  CE_UNSET(ib);
  CE_UNSET(vertDecl);


  iInstancedMesh* instancedMesh = device->CreateInstancedMesh();
  instancedMesh->SetMesh(mesh);
	instancedMesh->SetBoundingBox (ibbox);

	ceInstancedData id;
	id.Binding			=	ceProgramParameterName ("INSTANCEPOSITION");
	id.Type					= IDT_Vector4;
  id.Data         = 0;
	instancedMesh->AddInstanceData (id);

  ceGrassInstanceGenerator* gig = new ceGrassInstanceGenerator (grass_counter++);
	gig->SetVertices (instancePositions, realNum);
	instancedMesh->SetInstanceGenerator(gig);

  ceLODGenerator lodG;
  instancedMesh->SetLevelOfDetailSolver(lodG.CreateLinearSolver(instancedMesh->GetBoundingBox(), dp / 4.0f, false));


  return instancedMesh;
}


ceRef<iSkinMesh> create_cylinder_skin_mesh (ceEngine*	engine,
																				float			radius,
																				float			length,
																				unsigned	numRads,
																				unsigned	numSegments)
{
  iDevice* device = engine->GetDevice();
	ceVertexElement vertexElements []  =
		{ ceVertexElement(ceProgramParameterName(PPB_Position0),			DT_Float, 3, 0,  56, 0),
			ceVertexElement(ceProgramParameterName(PPB_Normal0),				DT_Float, 3, 12, 56, 0),
			ceVertexElement(ceProgramParameterName(PPB_TexCoord0),			DT_Float, 2, 24, 56, 0),
      ceVertexElement(ceProgramParameterName(PPB_Index),          DT_Float, 3, 32, 56, 0),
			ceVertexElement(ceProgramParameterName(PPB_Blend),					DT_Float, 3, 44, 56, 0),

			ceVertexElement()
		};

	struct SkinVertex
		{
			ceVector3f	p;
			ceVector3f	n;
      ceVector2f	t;
			ceVector3f	s;
			ceVector3f	b;
		};

  unsigned numVertices = (numRads+1) * (numSegments+1);
  unsigned numIndices = numSegments * numRads * 2 * 3;
  SkinVertex *vertices = new SkinVertex[numVertices];
  unsigned short *indices = new unsigned short [numIndices];

	for (unsigned s=0; s<numSegments; ++s)
		{
			unsigned y = (float)s * length / (float)numSegments;

			for (unsigned r=0; r<=numRads; ++r)
				{
					float co = cos ((float)r * M_PI * 2.0f /(float)numRads);
          float si = sin ((float)r * M_PI * 2.0f /(float)numRads);

					unsigned idx = s *(numRads+1) + r;
					vertices[idx].p = ceVector3f (co * radius, y, si * radius);
					vertices[idx].n = ceVector3f (co, 0, si);
          vertices[idx].t = ceVector2f ((float)r / (float)numRads, (float)s / (float)numSegments);
					vertices[idx].s = ceVector3f (s, 0, 0);
					vertices[idx].b = ceVector3f (1.0f, 0.0f, 0.0f);
				}
		}
  for (unsigned r=0; r<=numRads; ++r)
    {
      float co = cos ((float)r * M_PI * 2.0f /(float)numRads);
      float si = sin ((float)r * M_PI * 2.0f /(float)numRads);

      unsigned idx = numSegments * (numRads+1) + r;
      vertices[idx].p = ceVector3f (co * radius, length, si * radius);
      vertices[idx].n = ceVector3f (co, 0, si);
      vertices[idx].t = ceVector2f ((float)r / (float)numRads, 1.0f);
      vertices[idx].s = ceVector3f (numSegments-1, 0, 0);
      vertices[idx].b = ceVector3f (1.0f, 0.0f, 0.0f);
    }

  unsigned short *iptr = indices;
  for (unsigned s=0; s<numSegments; ++s)
    {
      unsigned i0 = s * (numRads + 1);
      unsigned i1 = i0 + numRads + 1;
      for (unsigned r=0; r<numRads; ++r)
        {
          unsigned i00 = i0 + r;
          unsigned i01 = i0 + r + 1;
          unsigned i10 = i1 + r;
          unsigned i11 = i1 + r + 1;

          *iptr++ = i00;
          *iptr++ = i01;
          *iptr++ = i11;

          *iptr++ = i00;
          *iptr++ = i11;
          *iptr++ = i10;
        }
    }


  iVertexDeclaration* vd = device->CreateVertexDeclaration(vertexElements);
  iVertexBuffer *vb = device->CreateVertexBuffer(numVertices * sizeof (SkinVertex), vertices, BDM_Static);
  iIndexBuffer *ib = device->CreateIndexBuffer(numIndices * sizeof (unsigned short), indices, BDM_Static);

  ceBoundingBox bbox;
  bbox.Clear();
  float max = length > radius ? length : radius;
  bbox.Add(max, max, max);
  bbox.Add(-max, -max, -max);
  bbox.Update();

  ceMesh* mesh = new ceMesh ();
  mesh->SetVertexDeclaration(vd);
  mesh->SetVertices(vb);
  mesh->SetIndices(ib);
  mesh->SetNumberOfTrigons(numIndices / 3);
  mesh->Set32BitIndices(false);
  mesh->SetBoundingBox(bbox);

  iSkinMesh *skinMesh = device->CreateSkinMesh();
  skinMesh->SetMesh(mesh);

  mesh->Release();
  return skinMesh;
}

ceRef<iMesh> create_plane_mesh (ceEngine* engine, float x, float y)
{
  iDevice* device = engine->GetDevice();
  float hx = x / 2.0f;
  float hy = y / 2.0f;
	ceVertexElement vertexElements []  =
    { ceVertexElement(ceProgramParameterName(PPB_Position0),	DT_Float, 3, 0,  56, 0),
      ceVertexElement(ceProgramParameterName(PPB_Normal0),		DT_Float, 3, 12, 56, 0),
      ceVertexElement(ceProgramParameterName(PPB_TexCoord0),	DT_Float, 2, 24, 56, 0),
      ceVertexElement(ceProgramParameterName(PPB_Tangent0),		DT_Float, 3, 32, 56, 0),
      ceVertexElement(ceProgramParameterName(PPB_BiNormal0),	DT_Float, 3, 44, 56, 0),

			ceVertexElement()
		};

	float vertices[] =
	{
    -hx, -hy, 0,    0, 0, 1,      0, 0,   x, 0, 0,   0, y, 0,
    -hx,  hy, 0,    0, 0, 1,      0, 1,   x, 0, 0,   0, y, 0,
     hx, -hy, 0,    0, 0, 1,      1, 0,   x, 0, 0,   0, y, 0,
     hx,  hy, 0,    0, 0, 1,      1, 1,   x, 0, 0,   0, y, 0,
	};

	unsigned short indices[] =
	{
		0, 1, 3,
		0, 3, 2
	};


	iVertexDeclaration* vertDecl = device->CreateVertexDeclaration(vertexElements);
	iVertexBuffer* vertexBuffer = device->CreateVertexBuffer(sizeof(vertices), vertices, BDM_Static);
	iIndexBuffer* indexBuffer = device->CreateIndexBuffer(sizeof(indices), indices, BDM_Static);

	ceBoundingBox bbox;
	bbox.Clear();
	bbox.Add(ceVector3f (-x, -y, -1));
	bbox.Add(ceVector3f ( x,  y,  1));
	bbox.Update();

	ceMesh* mesh = new ceMesh();
	mesh->SetVertexDeclaration(vertDecl);
	mesh->SetVertices(vertexBuffer);
	mesh->SetIndices(indexBuffer);
	mesh->Set32BitIndices(false);
	mesh->SetBoundingBox(bbox);
	mesh->SetNumberOfTrigons(2);
  CE_UNSET(vertDecl);
  CE_UNSET(vertexBuffer);
  CE_UNSET(indexBuffer);

	iStaticMesh* staticMesh = device->CreateStaticMesh();
	staticMesh->SetMesh(mesh);
	return staticMesh;
}
/*
iMesh* create_particle_mesh (iDevice* device, float size)
{
  ceVertexElement vertexElements []  =
    { ceVertexElement(VB_Position, 0, 3, 0,  20, 0),
      ceVertexElement(VB_TexCoord, 0, 2, 12, 20, 0),
      ceVertexElement(VB_Undefined)
		};
  iVertexDeclaration* vertDecl = device->CreateVertexDeclaration(vertexElements);

	float vertices[] = {
		-size, -size, 0,    0, 0,
		-size,  size, 0,    0, 1, 
		 size,  size, 0,    1, 1,
		 size, -size, 0,    1, 0
 	};

	unsigned short indices[] = {
		0, 1, 2,
		0, 2, 3
	};

  iVertexBuffer* vb = device->CreateVertexBuffer(sizeof(vertices), vertices, BDM_Static);
  iIndexBuffer* ib = device->CreateIndexBuffer(sizeof(indices), indices, BDM_Static);

  ceBoundingBox bbox;
	bbox.Clear();
  bbox.Add (ceVector3 (-size, -size, 0));
  bbox.Add (ceVector3 ( size,  size, 0));
	bbox.Update();

  ceMesh mesh;
  mesh.SetVertexDeclaration(vertDecl);
  mesh.SetVertices(vb);
  mesh.SetIndices(ib);
  mesh.Set32BitIndices(false);
  mesh.SetBoundingBox(bbox);
  mesh.SetNumberOfTrigons(2);

  ceBoundingBox ibbox;
	ibbox.Clear ();
  ibbox.Add (ceVector3 (-1000, -1000, -1000));
  ibbox.Add (ceVector3 ( 1000,  1000,  1000));
	ibbox.Update();

  iInstancedMesh* instancedMesh = engine.CreateInstancedMesh();
  instancedMesh->SetMesh(mesh);
	instancedMesh->SetBoundingBox (ibbox);

  ceParticleGenerator* gig = new ceParticleGenerator (100);
	instancedMesh->SetInstanceGenerator(gig);

  return instancedMesh;
}
*/

iMesh* create_static_plane (iDevice* device, float u, float v)
{
	ceVertexXYZNT2 vertices [] = 
		{
			{ ceVector3f (-u, 0,  v), ceVector3f (0, 1, 0), ceVector2f (0, 0) },
			{ ceVector3f (-u, 0, -v), ceVector3f (0, 1, 0), ceVector2f (0, 1) },
			{ ceVector3f ( u, 0, -v), ceVector3f (0, 1, 0), ceVector2f (1, 1) },
			{ ceVector3f ( u, 0,  v), ceVector3f (0, 1, 0), ceVector2f (1, 0) },
		};
	unsigned short indices [] = 
		{
			0, 1, 2, 
			0, 2, 3,
		};

	ceVertexElement vertexElements []  =
		{ 
      ceVertexElement(ceProgramParameterName(PPB_Position0), DT_Float, 3, 0,  32, 0),
      ceVertexElement(ceProgramParameterName(PPB_Normal0), DT_Float, 3, 12, 32, 0),
      ceVertexElement(ceProgramParameterName(PPB_TexCoord0), DT_Float, 2, 24, 32, 0),
			ceVertexElement()
		};
  iVertexDeclaration* vertDecl = device->CreateVertexDeclaration(vertexElements);
	iVertexBuffer* vb = device->CreateVertexBuffer(sizeof (vertices), vertices, BDM_Static);
  iIndexBuffer* ib = device->CreateIndexBuffer(sizeof(indices), indices, BDM_Static);
  ceMesh* mesh = new ceMesh();
  mesh->Set32BitIndices(false);
  mesh->SetVertexDeclaration(vertDecl);
  mesh->SetVertices (vb);
  mesh->SetIndices(ib);
  mesh->SetNumberOfTrigons(2);

  CE_UNSET(vertDecl);
  CE_UNSET(vb);
  CE_UNSET(ib);

	ceBoundingBox bbox;
	bbox.Clear();
	bbox.Add (-u, 0, -v);
	bbox.Add ( u, 0,  v);
	bbox.Update();
  mesh->SetBoundingBox (bbox);


  iStaticMesh* sm = device->CreateStaticMesh();
	sm->SetMesh (mesh);

	return sm;
}


void clean_up ()
{
  CE_UNSET(physWorld);
  CE_UNSET(movingLightNode);
  CE_UNSET(svTime);
  CE_UNSET(svWind);
  CE_UNSET(svSunPos);
}


bool init (iDevice* device)
{

  physWorld = new cmPhysicsWorld ();
  physWorld->Setup();
  physWorld->SetGravity(ceVector3f (0.0f, 0.0f, -9.81f));
  
	svTime = new ceSharedValueFloat		("Time", 0.0f);
	svWind = new ceSharedValueVector4 ("Wind", ceVector4f (1.0f, 0.0f, 0.0f, 0.5f));
  svSunPos = new ceSharedValueVector3 ("SunPos", ceVector3f (0.0f, 0.0f, 0.0f));
	ceSharedValueManager::Get ()->AddValue (svTime);
	ceSharedValueManager::Get ()->AddValue (svWind);
  ceSharedValueManager::Get ()->AddValue (svSunPos);

	ceMaterial* concreteMaterial = engine.GetAssetSystem()->GetAsset<ceMaterial>("materials/concrete.mat");
	if (!concreteMaterial)
    {
      printf ("No solid material found\n");
      fflush(stdout);
      return false;
    }

  ceMaterial* skinMaterial = engine.GetAssetSystem()->GetAsset<ceMaterial>("materials/concrete_skin.mat");
  if (!skinMaterial)
    {
      printf ("No skin material found\n");
      fflush(stdout);
      return false;
    }

  ceEntityNode* litNode = 0;
  iMesh* terrain  = create_terrain_mesh(&engine, "textures/terrain.png", false, false, 200.0f, 200.0f);
  iMesh* plane    = create_plane_mesh(&engine, 200.0f, 200.0f);


	ceSector* sector0 = new ceSector ();


  ceGeometry* groundGeom = new ceGeometry (plane, concreteMaterial);
  ceEntityNode* groundNode = new ceEntityNode ();
  groundNode->SetGeometry (groundGeom);
  groundNode->SetTranslation (ceVector3f (0, 0, 0));
  groundNode->FinishTransformation (false);


  skeleton = new ceSkeleton();
  ceSkeletonBone* bone0 = new ceSkeletonBone (A_Y, 30.0f);
  ceSkeletonBone* bone1 = new ceSkeletonBone (A_Y, 30.0f);
  ceSkeletonBone* bone2 = new ceSkeletonBone (A_Y, 30.0f);

  skeleton->AddBone(bone0);
  skeleton->AddBone(bone1, bone0);
  skeleton->AddBone(bone2, bone1);
  skeleton->Initialize();

  iSkinMesh* cylinderSkinMesh = create_cylinder_skin_mesh(&engine, 15.0f, 90.0f, 16, 3);
  cylinderSkinMesh->SetSkeleton(skeleton);
  ceGeometry *cylinderGeom = new ceGeometry (cylinderSkinMesh, skinMaterial);
  ceEntityNode *cylinderNode = new ceEntityNode ();
  cylinderNode->SetGeometry(cylinderGeom);
  cylinderNode->SetTranslation(ceVector3f (0, 0, 0));
  cylinderNode->FinishTransformation(false);


  /*
  ceMesh *cubeMesh = engine.GetAssetSystem()->GetAsset<ceMesh>(ceAssetLocator("meshes/my_cube/Cube.mesh"));
	if (cubeMesh)
		{
      cubeParent = new ceNode ();
      {
        iStaticMesh *staticCubeMesh = device->CreateStaticMesh();
        staticCubeMesh->SetMesh(cubeMesh);
        ceGeometry* cubeGeometry = new ceGeometry (staticCubeMesh, terrainMaterial);
        ceEntityNode* cubeEntity = new ceEntityNode();
        cubeEntity->SetGeometry(cubeGeometry);
        cubeEntity->SetTranslation(ceVector3f (20, 0, 0.0f));
        cubeParent->AddNode(cubeEntity);
      }

      {
        iStaticMesh *staticCubeMesh = device->CreateStaticMesh();
        staticCubeMesh->SetMesh(cubeMesh);
        ceGeometry* cubeGeometry = new ceGeometry (staticCubeMesh, terrainMaterial);
        ceEntityNode* cubeEntity = new ceEntityNode();
        cubeEntity->SetGeometry(cubeGeometry);
        cubeEntity->SetTranslation(ceVector3f (-20, 0, 0.0f));
        cubeParent->AddNode(cubeEntity);
        cubeParent1 = cubeEntity;
      }

      {
        iStaticMesh *staticCubeMesh = device->CreateStaticMesh();
        staticCubeMesh->SetMesh(cubeMesh);
        ceGeometry* cubeGeometry = new ceGeometry (staticCubeMesh, terrainMaterial);
        ceEntityNode* cubeEntity = new ceEntityNode();
        cubeEntity->SetGeometry(cubeGeometry);
        cubeEntity->SetTranslation(ceVector3f (5, 5, 5));
        cubeParent1->AddNode(cubeEntity);
      }

      {
        iStaticMesh *staticCubeMesh = device->CreateStaticMesh();
        staticCubeMesh->SetMesh(cubeMesh);
        ceGeometry* cubeGeometry = new ceGeometry (staticCubeMesh, terrainMaterial);
        ceEntityNode* cubeEntity = new ceEntityNode();
        cubeEntity->SetGeometry(cubeGeometry);
        cubeEntity->SetTranslation(ceVector3f (-5, -5, 5));
        cubeParent1->AddNode(cubeEntity);
      }

      cubeParent->SetTranslation(ceVector3f (0, 0, 20));
      cubeParent->FinishTransformation();

      sector0->AddNode(cubeParent);
		}
    */

	/*
	iPatchMesh* terrain1 = create_terrain(&engine, "texturens/terrain.png", true, false, 200.0f, 200.0f);
  ceGeometry* terrainGeom1 = new ceGeometry (terrain1, terrainMaterial);
  ceEntityNode* terrainNode1 = new ceEntityNode ();
	terrainNode1->SetGeometry (terrainGeom1);
  terrainNode1->SetTranslation (ceVector3f (0, 0, 0));
	terrainNode1->SetName ("Terrain1");
	terrainNode1->FinishTransformation (false);
	*/
	/*
  btStaticPlaneShape* planeShape = new btStaticPlaneShape (btVector3 (0, 0, 1), 1);
  ceMatrix4f mat;
  physWorld.AddStaticRigidBody(mat, planeShape);
	*/

	/*
	ceNode* node = ceLoadingSystem::Get ()->LoadNode ("", ceVFS::Get ()->Open ("meshes/portal.xgroup"));
	if (node)
		{
			ceTransformer t;
			t.AddNode (node);
			t.Finish ();
			t.GetTransformation ().SetTranslation (ceVector3f (0, 0, 20));
			t.UpdateTransformation ();
			sector0->AddNode (node);
		}
	*/
	/*
  srand(0);
#define RANDOM(min,max) ((float)(min) + ((float)rand () / (float)RAND_MAX) * (float)((max)-(min)))
  
	iMesh* sphereMesh = create_sphere(device, 5.0f, 17);
  ceGeometry* sphereGeom = new ceGeometry (sphereMesh, defaultMaterial);
  btSphereShape* sphereShape = new btSphereShape(5.0f);
  for (unsigned i=0; i<10; i++)
    {
      ceEntityNode* sphereNode = new ceEntityNode ();
      sphereNode->SetGeometry(sphereGeom);
      sphereNode->SetTranslation(ceVector3f (20 + RANDOM(-1, 1), 20 + RANDOM(-1, 1), 20 + i * 20));
      sphereNode->SetName("Sphere");
      sphereNode->SetCastShadow(true);
      sphereNode->FinishTransformation(false);

      physWorld.AddDynamicRigidBody(sphereNode, sphereShape);

      sector0->AddNode(sphereNode);
    }


#undef RANDOM
		*/


  /*
  ceMesh* cube = ceMeshManager::Get()->GetMesh(&engine, "meshes/something.xmesh");

  iStaticMesh* cubeMesh = engine.CreateStaticMesh();
  cubeMesh->SetMesh(cube);

  ceGeometry* cubeGeom = new ceGeometry (cubeMesh, surfaces[Surf_Ground]);
  ceEntityNode* cubeNode = new ceEntityNode ();
  cubeNode->SetGeometry (cubeGeom);
  cubeNode->SetTranslation (ceVector3f (0, 0, 20));
  cubeNode->SetName ("Cube");
  cubeNode->FinishTransformation (false);
  */

	/*
  ceMaterialManager::Get()->SetDefaultMaterial(defaultMaterial);

  ceGroupNode* gn = xdl.LoadGroupNode(&engine, ceVFS::Get()->Open("meshes/temple.xgroup"));

	*/



  ceLight* gLight1 = new ceLight();
	gLight1->SetAmbient (ceColor4f (0.0f, 0.0f, 0.0f));
	gLight1->SetDiffuse  (ceColor4f (1.0f, 1.0f, 1.0f));
  gLight1->SetSpecular (ceColor4f (1.0f, 1.0f, 1.0f));
	gLight1->SetRange (100.0f);
	gLight1->SetRangeMode (LRM_Const);

  ceVector3f lightPos (200.0f, 200.0f, 200.0f);

  ceLightObject* sLight1 = new ceLightObject(gLight1);
  sLight1->SetCastShadow(true);
  ceEntityNode* lightNode1 = new ceEntityNode();
  lightNode1->SetLight(sLight1);
  lightNode1->SetTranslation(lightPos);
  lightNode1->FinishTransformation(false);

  svSunPos->SetValue(lightPos.Normalized());

   movingLightNode = lightNode1;

  sector0->AddNode(groundNode);
  sector0->AddNode(cylinderNode);
	//sector0->AddNode (cubeNode);
  sector0->AddNode(lightNode1);


	/*
#if 0
	ceSkyBox *skyBox = new ceSkyBox ();
  skyBox->Initialize (device, ceVector3f (590.0f, 590.0f, 590.0f));
	skyBox->GetGeometry ()->SetMaterial (simple_sky_mat);
	sector0->AddNode (skyBox);
#else
  ceSkySphere *skySphere = new ceSkySphere ();
  skySphere->Initialize(device, 500.0f);
//  skySphere->GetGeometry()->SetMaterial(sunshine_sky_mat);
  sector0->AddNode(skySphere);
#endif
	*/

  ceCamera* camera = new ceCamera();
	camera->SetSector (sector0);
  cameraNode = new ceEntityNode();
  cameraNode->SetCamera(camera);
  cameraNode->ClearTransformation();
  cameraNode->SetTranslation(ceVector3f (0, 100, 0));
  cameraNode->GlobalLookAt(ceVector3f (0, 0, 0), ceVector3f (0, 0, 1));
  cameraNode->FinishTransformation(false);


	/*
	portal = sector0->Connect (sector1);
  portal->AddVector (ceVector3f (100, -100,   0));
  portal->AddVector (ceVector3f (100, -100, 100));
  portal->AddVector (ceVector3f (100,  100,   0));
  portal->AddVector (ceVector3f (100,  100, 100));
	*/

  /*
  unsigned totalGrasses = 10000;
  unsigned parts = 8;
  unsigned partGrasses = totalGrasses / (parts*parts);
  float partAmount = 1.0f / (float)parts;
  for (unsigned p=0; p<parts; p++)
    {
      float p0 = p * partAmount;
      float p1 = p0 + partAmount;

      for(unsigned q=0; q<parts; q++)
        {
          float q0 = q * partAmount;
          float q1 = q0 + partAmount;

          iMesh* grassMesh = create_grass_mesh(device, 
                                               2.5, 
                                               3, 
                                               partGrasses, 
																							 "textures/terrain.png",
																							 "textures/density_terrain.jpg",
                                               p0, p1, q0, q1, 200.0f, 200.0f,
																							 false, false);
          ceGeometry* grassGeometry = new ceGeometry (grassMesh, surfaces[Surf_Grass]);
          ceEntityNode* grassNode = new ceEntityNode();
          grassNode->SetCastShadow(true);
          grassNode->SetGeometry(grassGeometry);
          grassNode->SetTranslation(ceVector3f (0, 0, 0));
          grassNode->FinishTransformation(true);
          sector0->AddNode(grassNode);
        }
    }

	for (unsigned p=0; p<parts; p++)
    {
      float p0 = p * partAmount;
      float p1 = p0 + partAmount;

      for(unsigned q=0; q<parts; q++)
        {
          float q0 = q * partAmount;
          float q1 = q0 + partAmount;

          iMesh* grassMesh = create_grass_mesh(device, 
                                               2.5, 
                                               3, 
                                               partGrasses, 
																							 "textures/terrain.png",
																							 "textures/density_terrain.jpg",
                                               p0, p1, q0, q1, 200.0f, 200.0f,
																							 true, false);
          ceGeometry* grassGeometry = new ceGeometry (grassMesh, surfaces[Surf_Grass]);
          ceEntityNode* grassNode = new ceEntityNode();
          grassNode->SetCastShadow(true);
          grassNode->SetGeometry(grassGeometry);
          grassNode->SetTranslation(ceVector3f (200, 0, 0));
          grassNode->FinishTransformation(true);
          sector1->AddNode(grassNode);
        }
    }
  */

  float a = 768.0f / 1024.0f;
  projector.SetLeft(-1.0f);
  projector.SetRight(1.0f);
  projector.SetBottom(-a);
  projector.SetTop(a);
  projector.SetNear(1.0f);
  projector.SetFar(1024.0f);
  projector.FinishTransformation();

  scene.AddSector(sector0);


	scene.FindSectorLights ();
	scene.ApplyLights ();

  return true;
}

void debug_matrix (const ceMatrix4f& mat)
{
  printf ("Matrix:\n");
	printf ("  %.2f %.2f %.2f %.2f\n", mat._00, mat._01, mat._02, mat._03);
	printf ("  %.2f %.2f %.2f %.2f\n", mat._10, mat._11, mat._12, mat._13);
	printf ("  %.2f %.2f %.2f %.2f\n", mat._20, mat._21, mat._22, mat._23);
  printf ("  %.2f %.2f %.2f %.2f\n\n", mat._30, mat._31, mat._32, mat._33);
  fflush (stdout);
}


void render (iDevice* device)
{
  timeValue = SDL_GetTicks();

  unsigned timeDiff = timeValue - timeStart;

  float time = timeDiff / 1000.0f;
	svTime->SetValue (time);

  float windRot = time * 0.1f;
	svWind->SetValue (ceVector4f (cos(windRot), sin(windRot), 0.0f, 0.25f));

  float lightRot = time * 1.0f;
	
  if (movingLightNode)
    {
      ceMatrix4f sunMat;
      sunMat.SetRotation(ceVector3f (1.0f, 0.0f, 0.0), lightRot * 180.0f / M_PI);
      ceVector3f sunPos = sunMat * ceVector3f (0.0f, 1000.0f, 0.0f);
      svSunPos->SetValue(sunPos.Normalized());
      movingLightNode->SetTranslation(ceVector3f (cos(lightRot) * 50.0f, sin (lightRot) * 50.0f, 50.0f));
      movingLightNode->FinishTransformation(false);
    }

  if (cubeParent)
    {
      static float cpAngle = 0;
      cubeParent->SetRotation(ceVector3f (0, 0, 1), cpAngle);
      cubeParent1->SetRotation(ceVector3f (0, 0, 1), 2.0f * cpAngle);
      cubeParent->FinishTransformation();
      cpAngle += 0.01f;
    }


  if (skeleton)
    {
      static float skelRot = 0.0f;

      ceMatrix4f m;
      m.SetRotationX(skelRot);
      skeleton->GetBone(0)->SetOrientation(m);

      m.SetRotationX(sin(skelRot * 0.5f) * 0.5f);
      skeleton->GetBone(1)->SetOrientation(m);
      m.SetRotationX(cos(skelRot * 0.5f) * 0.5f);
      skeleton->GetBone(2)->SetOrientation(m);


      skeleton->Update();
      skelRot += 0.001f;
    }


  if (renderWireframe)
    {
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }
  else
    {
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }
  physWorld->Step();

  renderLoop->Render(device, &scene, *cameraNode->GetCamera(), projector);

}





#ifdef __WIN32__
//int WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
#else
#endif
int main (int argc, char** argv)
{
	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);

  timeStart = SDL_GetTicks();

  ceEngine::Config config;
  memset(&config, 0, sizeof(ceEngine::Config));
	config.windowCreate = true;
  config.windowWidth = WINDOW_WIDTH;
  config.windowHeight = WINDOW_HEIGHT;
  config.windowFullscreen = FULLSCREEN;

	if (!engine.ReadCommandLineParams (argc, argv))
		{
			printf ("False Pathname\n");
      clean_up ();
      SDL_Quit();
			return -1;
		}

	if (!engine.Initialize(config))
	  {
	    printf ("Unable to configure engine\n");
      clean_up ();
      SDL_Quit();
			return -1;
	  }


  iDevice* device = engine.GetDevice();
  if (!mc.Initialize (device))
		{
      clean_up ();
      SDL_Quit();
			return -1;
		}

  renderLoop = new ceRenderLoop();
  if (!renderLoop->Initialize(device))
    {
      clean_up ();
      SDL_Quit();
			return -1;
    }
  renderLoop->SetShadowMapDistances(5, 10, 20);
  renderLoop->SetViewport (device, engine.GetWindow());
	renderLoop->SetShadowEnabled (false);


  if (!init (device))
    {
      printf ("Unable to initialize the data\n");
      clean_up ();
      SDL_Quit();
			return 0;
		}
	printf ("Initialised\n");


  SDL_WM_GrabInput(SDL_GRAB_ON);
  SDL_ShowCursor(0);

	bool running = true;
  unsigned frames = 0;

  unsigned lastTime = SDL_GetTicks();
  SDL_Event e;
  memset (&e, 0, sizeof (e));
  while (running)
		{
      clear_input();
			while (SDL_PollEvent(&e))
				{
			    running = handle_input(e);
					switch (e.type)
						{
							case SDL_KEYUP:
                if (e.key.keysym.sym == SDLK_r)
                  {
                    renderWireframe = !renderWireframe;
                  }
                if (e.key.keysym.sym == SDLK_b)
                  {
                    renderBoundingBoxes = !renderBoundingBoxes;
                  }
                if (e.key.keysym.sym == SDLK_ESCAPE)
                  {
                    running = false;
                  }
                break;
						}
				}
      handle_camera();
      frames++;
      unsigned currentTime = SDL_GetTicks();
      if (currentTime - lastTime >= 1000)
        {
          char str[256];
          sprintf (str, "VisionGL: FPS: %d", frames);
          frames = 0;
//          printf ("%s\n", str);
//          fflush(stdout);
          lastTime = currentTime;
          SDL_WM_SetCaption(str, 0);
        }
		
      render (device);
      SDL_GL_SwapBuffers();
		}
  clean_up ();
  SDL_WM_GrabInput(SDL_GRAB_OFF);
  SDL_ShowCursor(1);

  return 0;
}
